package directory

import (
	"context"
	"fmt"
	"strconv"
	"strings"
	"time"

	"mall/common/globalkey"
	"mall/common/modelx"
	"mall/service/attachment/model"
	"mall/service/attachment/rpc/internal/svc"
	"mall/service/attachment/rpc/internal/utils/errorhandler"
	"mall/service/attachment/rpc/types/attachment"
	"mall/service/backend/common/errorx"
	"mall/service/backend/common/utils/pointy"

	"github.com/qiniu/go-sdk/v7/auth/qbox"
	"github.com/qiniu/go-sdk/v7/storage"
	"github.com/sirupsen/logrus"
	"github.com/zeromicro/go-zero/core/logx"
)

type BackendGetRecordListLogic struct {
	ctx           context.Context
	svcCtx        *svc.ServiceContext
	bucketManager *storage.BucketManager
	bucket        string
	logx.Logger
}

func NewBackendGetRecordListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BackendGetRecordListLogic {
	return &BackendGetRecordListLogic{
		ctx:    ctx,
		svcCtx: svcCtx,
		Logger: logx.WithContext(ctx),
	}
}

func (l *BackendGetRecordListLogic) BackendGetRecordList(in *attachment.RecordListReq) (*attachment.RecordListResp, error) {
	var diagnose uint32
	if in.Diagnose != nil {
		diagnose = *in.Diagnose
	}

	if diagnose == 0 {
		return l.backendGetRecordList(in)
	}

	mac := qbox.NewMac(l.svcCtx.Config.QiniuConf.AccessKey, l.svcCtx.Config.QiniuConf.SecretKey)

	cfg := storage.Config{
		UseHTTPS:      l.svcCtx.Config.QiniuConf.UseHTTPS, // 是否使用https域名进行资源管理
		UseCdnDomains: l.svcCtx.Config.QiniuConf.UseCdnDomains, // 上传是否使用CDN上传加速
	}
	// 指定空间所在的区域，如果不指定将自动探测
	// 如果没有特殊需求，默认不需要指定
	// cfg.Region=&storage.ZoneHuabei
	switch l.svcCtx.Config.QiniuConf.Region {
	case "z2":
		cfg.Region = &storage.ZoneHuanan
	}

	l.bucketManager = storage.NewBucketManager(mac, &cfg)
	l.bucket = l.svcCtx.Config.QiniuConf.Bucket

	var count int
	var marker string
	var err error
	switch diagnose {
	case 1: // 获取指定前缀的文件列表
		count, marker, err = l.qiniuListFiles(in)
	case 11: // 批量获取文件信息
		count, err = l.qiniuStat(in)
	case 12: // 批量修改文件类型
		count, err = l.qiniuChangeMime(in)
	case 13: // 批量修改文件存储类型
		if in.Mime != nil && len(strings.TrimSpace(*in.Mime)) > 0 {
			count, err = l.qiniuChangeType(in)
		}
	}

	if err != nil {
		return nil, err
	}

	resp := &attachment.RecordListResp{
		Total:  uint64(count),
		Marker: marker,
	}

	return resp, nil
}

func (l *BackendGetRecordListLogic) backendGetRecordList(in *attachment.RecordListReq) (*attachment.RecordListResp, error) {
	var orderBys []string
	if len(in.OrderBy) > 0 {
		for _, orderBy := range in.OrderBy {
			var order string = " ASC"
			if orderBy.Order == "1" {
				order = " DESC"
			}

			switch orderBy.Field {
			case "id":
				orderBys = append(orderBys, model.AttachmentFileInfoColumns.ID + order)
			}
		}
	}

	if len(orderBys) == 0 {
		orderBys = append(orderBys, model.AttachmentFileInfoColumns.ID)
	}

	var stati string
	if len(in.FilterBy) > 0 {
		for _, filterBy := range in.FilterBy {
			if filterBy.Field == "status" {
				stati = strings.TrimSpace(filterBy.In)
			}
		}
	}

	var page uint64
	var pageSize uint64
	var restricted bool
	filterMap := make(map[string]map[string][]any)
	if in.Ids != nil {
		if vals := modelx.UniqueInterfaces(strings.TrimSpace(*in.Ids), 64); len(vals) > 0 {
			filterMap[model.AttachmentFileInfoColumns.ID] = map[string][]any{
				"in": vals,
			}
			restricted = true
		}
	}
	if !restricted {
		page = in.Page
		pageSize = in.PageSize
	}

	statusIn := modelx.UniqueInterfaces(stati, 32)
	statusMap := make(map[string][]any)
	if len(statusIn) > 0 {
		statusMap["in"] = statusIn
	}
	if in.Status != nil && *in.Status > 0 {
		statusMap["equalTo"] = []any{*in.Status - 1}
	}
	filterMap[model.AttachmentFileInfoColumns.DelStatus] = statusMap

	if in.Key != nil {
		filterMap[model.AttachmentFileInfoColumns.KeyValue] = map[string][]any{
			"equalTo": []any{strings.TrimSpace(*in.Key)},
		}
	}
	if in.FileId != nil {
		filterMap[model.AttachmentFileInfoColumns.AttachmentID] = map[string][]any{
			"equalTo": []any{*in.FileId},
		}
	}
	if in.ParentId != nil {
		filterMap[model.AttachmentFileInfoColumns.ParentID] = map[string][]any{
			"equalTo": []any{*in.ParentId},
		}
	}

	var orderBy string
	if len(orderBys) > 0 {
		orderBy = strings.Join(orderBys, ",")
	}
	res, count, err := l.svcCtx.FileInfo.GetList(l.ctx, model.M{
		"page":     page,
		"pageSize": pageSize,
		"orderBy":  orderBy,
	}, 1, filterMap)
	if err != nil {
		return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
	}

	resp := &attachment.RecordListResp{
		Total: uint64(count),
	}
	for _, v := range res {
		var updatedAt *string
		if v.PutTime > 0 {
			updatedAt = pointy.GetPointer(modelx.FormatTime(time.Unix(0, int64(v.PutTime)*100), ""))
		}
		var expiredAt *string
		if v.Expiration > 0 {
			expiredAt = pointy.GetPointer(modelx.FormatTime(time.Unix(int64(v.Expiration), 0), ""))
		}
		var meta *string
		if v.MetaValue.Valid {
			meta = pointy.GetPointer(v.MetaValue.String)
		}
		resp.List = append(resp.List, &attachment.RecordInfo{
			Id:            pointy.GetPointer(strconv.FormatUint(v.ID, 10)),
			FileId:        &v.AttachmentID,
			ParentId:      &v.ParentID,
			Bucket:        &v.Bucket,
			Key:           &v.KeyValue,
			Size:          &v.Size,
			Hash:          &v.Hash,
			MimeType:      &v.MimeType,
			Type:          pointy.GetPointer(uint32(v.Type)),
			Status:        pointy.GetPointer(uint32(v.Status)),
			RestoreStatus: pointy.GetPointer(uint32(v.RestoreStatus)),
			Md5:           &v.MD5,
			EndUser:       &v.EndUser,
			MetaValue:     meta,
			DelStatus:     pointy.GetPointer(uint32(v.DelStatus)),
			UpdatedAt:     updatedAt,
			ExpiredAt:     expiredAt,
		})
	}

	return resp, nil
}

// ListFiles 用来获取空间文件列表，可以根据需要指定文件的前缀 prefix，文件的目录 delimiter，循环列举的时候下次
// 列举的位置 marker，以及每次返回的文件的最大数量limit，其中limit最大为1000。
func (l *BackendGetRecordListLogic) qiniuListFiles(in *attachment.RecordListReq) (int, string, error) {
	var limit int
	if in.PageSize > 0 {
		limit = int(in.PageSize)
	} else {
		limit = 100
	}
	var prefix string
	if in.Prefix != nil {
		prefix = *in.Prefix
	}
	delimiter := ""
	// 初始列举marker为空
	var marker string
	if in.Marker != nil {
		marker = *in.Marker
	}
	var count int
	if in.Page > 0 {
		count = int(in.Page)
	} else {
		count = 1
	}
	var counter int
	var total int
	for {
		entries, commonPrefixes, nextMarker, hasNext, err := l.bucketManager.ListFiles(l.bucket, prefix, delimiter, marker, limit)
		if err != nil {
			logrus.Info(fmt.Sprintf("Qiniu ListFiles err: %+v", err))
			return total, marker, errorx.NewInternalError("file.listFilesFailed")
		}

		logrus.Info(fmt.Sprintf("Qiniu ListFiles commonPrefixes: %+v", commonPrefixes))

		// print entries
		for _, entry := range entries {
			filterMap := make(map[string]map[string][]any)
			filterMap[model.AttachmentFileInfoColumns.ParentID] = map[string][]any{
				"equalTo": []any{0},
			}
			filterMap[model.AttachmentFileInfoColumns.KeyValue] = map[string][]any{
				"equalTo": []any{entry.Key},
			}
			filterMap[model.AttachmentFileInfoColumns.DelStatus] = map[string][]any{
				"notEqualTo": []any{globalkey.StatusDeleted},
			}
			list, _, err := l.svcCtx.FileInfo.GetList(l.ctx, model.M{
				"pageSize": uint64(1),
			}, -1, filterMap)
			if err != nil {
				return total, marker, errorhandler.DefaultRepositoryError(l.Logger, err, in)
			}

			if len(list) > 0 {
				continue
			}

			var data model.AttachmentFileInfo
			data.KeyValue = entry.Key
			data.Bucket = l.bucket
			data.PutTime = uint64(entry.PutTime)
			data.Hash = entry.Hash
			data.Size = uint64(entry.Fsize)
			data.MimeType = entry.MimeType
			data.EndUser = entry.EndUser
			data.Type = uint8(entry.Type)
			data.Status = uint8(entry.Status)
			data.MD5 = entry.Md5

			err = l.svcCtx.FileInfo.Insert(l.ctx, nil, &data)
			if err != nil {
				return total, marker, errorhandler.DefaultRepositoryError(l.Logger, err, in)
			}
		}

		total += len(entries)

		if !hasNext {
			// list end
			break
		}

		marker = nextMarker
		counter += 1
		if counter >= count {
			break
		}
	}

	return total, marker, nil
}

func (l *BackendGetRecordListLogic) getList(
	in *attachment.RecordListReq,
) ([]*model.AttachmentFileInfo, error) {
	var orderBys []string
	if len(in.OrderBy) > 0 {
		for _, orderBy := range in.OrderBy {
			var order string = " ASC"
			if orderBy.Order == "1" {
				order = " DESC"
			}

			switch orderBy.Field {
			case "id":
				orderBys = append(orderBys, model.AttachmentFileInfoColumns.ID + order)
			}
		}
	}

	var stati string
	if len(in.FilterBy) > 0 {
		for _, filterBy := range in.FilterBy {
			if filterBy.Field == "status" {
				stati = strings.TrimSpace(filterBy.In)
			}
		}
	}

	var page uint64
	var pageSize uint64
	var restricted bool
	filterMap := make(map[string]map[string][]any)
	if in.Ids != nil {
		if vals := modelx.UniqueInterfaces(strings.TrimSpace(*in.Ids), 64); len(vals) > 0 {
			filterMap[model.AttachmentFileInfoColumns.ID] = map[string][]any{
				"in": vals,
			}
			restricted = true
		}
	}
	if !restricted {
		page = in.Page
		pageSize = in.PageSize
	}

	statusIn := modelx.UniqueInterfaces(stati, 32)
	statusMap := make(map[string][]any)
	if len(statusIn) > 0 {
		statusMap["in"] = statusIn
	}
	if in.Status != nil && *in.Status > 0 {
		statusMap["equalTo"] = []any{*in.Status - 1}
	}
	filterMap[model.AttachmentFileInfoColumns.DelStatus] = statusMap

	var orderBy string
	if len(orderBys) > 0 {
		orderBy = strings.Join(orderBys, ",")
	}
	list, _, err := l.svcCtx.FileInfo.GetList(l.ctx, model.M{
		"page":     page,
		"pageSize": pageSize,
		"orderBy":  orderBy,
	}, -1, filterMap)
	if err != nil {
		return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
	}

	return list, err
}

func (l *BackendGetRecordListLogic) qiniuStat(in *attachment.RecordListReq) (int, error) {
	var total int
	list, err := l.getList(in)
	if err != nil {
		return total, err
	}

	// 每个batch的操作数量不可以超过1000个，如果总数量超过1000，需要分批发送
	statOps := make([]string, 0, len(list))
	hashMap := make(map[string][]*model.AttachmentFileInfo)
	for _, v := range list {
		statOps = append(statOps, storage.URIStat(l.bucket, v.KeyValue))
		hashMap[v.Hash] = append(hashMap[v.Hash], v)
	}

	rets, err := l.bucketManager.Batch(statOps)
	if len(rets) == 0 {
		// 处理错误
		if e, ok := err.(*storage.ErrorInfo); ok {
			logrus.Info(fmt.Sprintf("Qiniu batch Stat detailed err: %+v", e))
		} else {
			logrus.Info(fmt.Sprintf("Qiniu batch Stat err: %+v", err))
		}

		return total, errorx.NewInternalError("file.listFilesFailed")
	}

	for _, ret := range rets {
		// 200 为成功
		if ret.Code == 200 {
			logrus.Info(fmt.Sprintf("Qiniu batch Stat: %+v", ret))
			// {Code:200 Data:{Fsize:2013 Hash:Fmc9IxNVnWGu9c7ekbigt5LJ4B5d MimeType:image/png Type:0 PutTime:16220307119872806 RestoreStatus:<nil> Status:<nil> Md5:588fb7cbc4caf8b306683864ff4ee2b4 EndUser: Expiration:<nil> TransitionToIA:<nil> TransitionToArchive:<nil> TransitionToDeepArchive:<nil> Error:}}
			// {Code:200 Data:{Fsize:960 Hash:FhzkYxMlFeE6PELww3KoAeLXRa7d MimeType:image/png Type:0 PutTime:16220307122033974 RestoreStatus:<nil> Status:<nil> Md5:d68f01167e42f1bd04b40f364b488362 EndUser: Expiration:<nil> TransitionToIA:<nil> TransitionToArchive:<nil> TransitionToDeepArchive:<nil> Error:}}
		} else {
			logrus.Info(fmt.Sprintf("Qiniu batch Stat data err: %+v", ret.Data.Error))

			return total, errorx.NewInternalError("file.listFilesFailed")
		}
	}

	tx, err := l.svcCtx.DB.BeginTx(l.ctx, nil)
	if err != nil {
		return total, errorhandler.DefaultRepositoryError(l.Logger, err, in)
	}

	for _, ret := range rets {
		if entries, ok := hashMap[ret.Data.Hash]; ok {
			for _, entry := range entries {
				entry.Size = uint64(ret.Data.Fsize)
				entry.MimeType = ret.Data.MimeType
				entry.Type = uint8(ret.Data.Type)
				entry.PutTime = uint64(ret.Data.PutTime)
				if ret.Data.RestoreStatus != nil {
					entry.RestoreStatus = uint8(*ret.Data.RestoreStatus)
				}
				if ret.Data.Status != nil {
					entry.Status = uint8(*ret.Data.Status)
				}
				entry.MD5 = ret.Data.Md5
				entry.EndUser = ret.Data.EndUser
				if ret.Data.Expiration != nil {
					entry.Expiration = uint64(*ret.Data.Expiration)
				}
				if ret.Data.TransitionToIA != nil {
					entry.TransitionToIa = uint64(*ret.Data.TransitionToIA)
				}
				if ret.Data.TransitionToArchive != nil {
					entry.TransitionToArchive = uint64(*ret.Data.TransitionToArchive)
				}
				if ret.Data.TransitionToDeepArchive != nil {
					entry.TransitionToDeepArchive = uint64(*ret.Data.TransitionToDeepArchive)
				}

				err := l.svcCtx.FileInfo.Update(l.ctx, tx, entry)
				if err != nil {
					tx.Rollback()
					return total, errorhandler.DefaultRepositoryError(l.Logger, err, in)
				}
				total += 1
			}
		}
	}

	tx.Commit()

	return total, nil
}

func (l *BackendGetRecordListLogic) qiniuChangeMime(in *attachment.RecordListReq) (int, error) {
	var total int
	list, err := l.getList(in)
	if err != nil {
		return total, err
	}

	var newMime string = strings.TrimSpace(*in.Mime)
	// 每个batch的操作数量不可以超过1000个，如果总数量超过1000，需要分批发送
	chgmOps := make([]string, 0, len(list))
	statOps := make([]string, 0, len(list))
	hashMap := make(map[string][]*model.AttachmentFileInfo)
	for _, v := range list {
		chgmOps = append(chgmOps, storage.URIChangeMime(l.bucket, v.KeyValue, newMime))
		statOps = append(statOps, storage.URIStat(l.bucket, v.KeyValue))
		hashMap[v.Hash] = append(hashMap[v.Hash], v)
	}

	rets, err := l.bucketManager.Batch(chgmOps)
	if len(rets) == 0 {
		// 处理错误
		if e, ok := err.(*storage.ErrorInfo); ok {
			logrus.Info(fmt.Sprintf("Qiniu batch ChangeMime detailed err: %+v", e))
		} else {
			logrus.Info(fmt.Sprintf("Qiniu batch ChangeMime err: %+v", err))
		}

		return total, errorx.NewInternalError("file.listFilesFailed")
	}

	for _, ret := range rets {
		// 200 为成功
		if ret.Code == 200 {
			logrus.Info(fmt.Sprintf("Qiniu batch ChangeMime: %+v", ret))
			// {Code:200 Data:{Fsize:0 Hash: MimeType: Type:0 PutTime:0 RestoreStatus:<nil> Status:<nil> Md5: EndUser: Expiration:<nil> TransitionToIA:<nil> TransitionToArchive:<nil> TransitionToDeepArchive:<nil> Error:}} 
			// {Code:200 Data:{Fsize:0 Hash: MimeType: Type:0 PutTime:0 RestoreStatus:<nil> Status:<nil> Md5: EndUser: Expiration:<nil> TransitionToIA:<nil> TransitionToArchive:<nil> TransitionToDeepArchive:<nil> Error:}}
		} else {
			logrus.Info(fmt.Sprintf("Qiniu batch ChangeMime err: %+v", ret.Data.Error))

			return total, errorx.NewInternalError("file.listFilesFailed")
		}
	}

	rs, err := l.bucketManager.Batch(statOps)
	if len(rs) == 0 {
		// 处理错误
		if e, ok := err.(*storage.ErrorInfo); ok {
			logrus.Info(fmt.Sprintf("Qiniu batch Stat detailed err: %+v", e))
		} else {
			logrus.Info(fmt.Sprintf("Qiniu batch Stat err: %+v", err))
		}

		return total, errorx.NewInternalError("file.listFilesFailed")
	}

	for _, ret := range rs {
		// 200 为成功
		if ret.Code == 200 {
			logrus.Info(fmt.Sprintf("Qiniu batch Stat: %+v", ret))
			// {Code:200 Data:{Fsize:2013 Hash:Fmc9IxNVnWGu9c7ekbigt5LJ4B5d MimeType:image/png Type:0 PutTime:16220307119872806 RestoreStatus:<nil> Status:<nil> Md5:588fb7cbc4caf8b306683864ff4ee2b4 EndUser: Expiration:<nil> TransitionToIA:<nil> TransitionToArchive:<nil> TransitionToDeepArchive:<nil> Error:}}
			// {Code:200 Data:{Fsize:960 Hash:FhzkYxMlFeE6PELww3KoAeLXRa7d MimeType:image/png Type:0 PutTime:16220307122033974 RestoreStatus:<nil> Status:<nil> Md5:d68f01167e42f1bd04b40f364b488362 EndUser: Expiration:<nil> TransitionToIA:<nil> TransitionToArchive:<nil> TransitionToDeepArchive:<nil> Error:}}
		} else {
			logrus.Info(fmt.Sprintf("Qiniu batch Stat err: %+v", ret.Data.Error))

			return total, errorx.NewInternalError("file.listFilesFailed")
		}
	}

	tx, err := l.svcCtx.DB.BeginTx(l.ctx, nil)
	if err != nil {
		return total, errorhandler.DefaultRepositoryError(l.Logger, err, in)
	}

	for _, ret := range rs {
		if entries, ok := hashMap[ret.Data.Hash]; ok {
			for _, entry := range entries {
				entry.Size = uint64(ret.Data.Fsize)
				entry.MimeType = ret.Data.MimeType
				entry.Type = uint8(ret.Data.Type)
				entry.PutTime = uint64(ret.Data.PutTime)
				if ret.Data.RestoreStatus != nil {
					entry.RestoreStatus = uint8(*ret.Data.RestoreStatus)
				}
				if ret.Data.Status != nil {
					entry.Status = uint8(*ret.Data.Status)
				}
				entry.MD5 = ret.Data.Md5
				entry.EndUser = ret.Data.EndUser
				if ret.Data.Expiration != nil {
					entry.Expiration = uint64(*ret.Data.Expiration)
				}
				if ret.Data.TransitionToIA != nil {
					entry.TransitionToIa = uint64(*ret.Data.TransitionToIA)
				}
				if ret.Data.TransitionToArchive != nil {
					entry.TransitionToArchive = uint64(*ret.Data.TransitionToArchive)
				}
				if ret.Data.TransitionToDeepArchive != nil {
					entry.TransitionToDeepArchive = uint64(*ret.Data.TransitionToDeepArchive)
				}

				err := l.svcCtx.FileInfo.Update(l.ctx, tx, entry)
				if err != nil {
					tx.Rollback()
					return total, errorhandler.DefaultRepositoryError(l.Logger, err, in)
				}
				total += 1
			}
		}
	}

	tx.Commit()

	return total, nil
}

func (l *BackendGetRecordListLogic) qiniuChangeType(in *attachment.RecordListReq) (int, error) {
	// todo
	return 0, nil
}
